/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.game;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.groupme.exceptions.GameAlreadyExistingForThisUsernameException;
import net.groupme.exceptions.GameNotFoundException;
import net.groupme.exceptions.InsertFailedException;
import net.groupme.model.Group;
import net.groupme.model.ObjectFactory;
import net.groupme.model.User;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * created on 11.02.2008
 * 
 * This class/interface...
 * 
 * @author Mischa Frank, <a href="mailto:code@planetfrank.net">code@planetfrank.net</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.6 $ $Date: 2008-03-04 17:06:26 $
 */

public class GameManager {
	private static GameManager INSTANCE = null;
	private List<GroupMeGame> gamesRunning = new ArrayList<GroupMeGame>();
	private List<GroupMeGame> gamesQueued = new ArrayList<GroupMeGame>();
	private List<GroupMeGame> gamesFinished = new ArrayList<GroupMeGame>();
	/** maps between the time-based ID and the ID the game has in the DB */
	public Map<Long, Long> TEMP_ID_TO_DB_ID_MAP = new HashMap<Long, Long>();
	
	private GameManager() {
		// only for blocking instanciation
	}
	
	/**
	 * @return the actual Instance of the UserList-Singleton
	 */
	public static GameManager getInstance() {
		if(INSTANCE == null) {
			INSTANCE = new GameManager();
		}
		return INSTANCE;
	}
	
	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * This method returns the gamesRunning of a GameManager object.
	 * @return the gamesRunning
	 */
	public List<GroupMeGame> getGamesRunning() {
		return this.gamesRunning;
	}

	/**
	 * This method returns the gamesQueued of a GameManager object.
	 * @return the gamesQueued
	 */
	public List<GroupMeGame> getGamesQueued() {
		return this.gamesQueued;
	}

	/**
	 * This method adds a game to the gamesQueued list of a GameManager object.
	 * @param game the game to add
	 * @throws GameAlreadyExistingForThisUsernameException 
	 */
	public void addGameToQueue(GroupMeGame game) throws GameAlreadyExistingForThisUsernameException {
		if (this.isGameExisting(game.getUsername())) {
			throw new GameAlreadyExistingForThisUsernameException();
		}
		logger.info("Added game to queue. Game-ID: " + game.getId());
		this.gamesQueued.add(game);
	}
	
	/**
	 * This method checks if there is already an open or queued game for a given Username in the gamelist.
	 * @param username
	 * @return true if a game is existing, otherwise false
	 */
	public boolean isGameExisting(String username) {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (username.equals(game.getUsername())){
				return true;
			}
		}
		iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (username.equals(game.getUsername())){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * This method checks if there is already an open or queued game for a given gameId
	 * in the gamelists.
	 * @param id the id of the game
	 * @return true if a game is existing, otherwise false
	 */
	public boolean isGameExisting(long id) {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				return true;
			}
		}
		iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * This method checks if there is a game with the given id in the game queue 
	 * @param id the id of the game
	 * @return true if a game is existing, otherwise false
	 */
	public boolean isGameInQueue(long id) {
		Iterator<GroupMeGame> iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * This method checks if there is a game with the given id in the game queue 
	 * @param id the id of the game
	 * @return true if a game is existing, otherwise false
	 */
	public boolean isGameRunning(long id) {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * This method returns one Game from GamesQueued or GamesRunning list of the GameManager 
	 * by the ID of the game.
	 * @param id the id of the game
	 * @return the game, null if not in list
	 * @throws GameNotFoundException 
	 */
	public GroupMeGame getGameByID(long id) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id == game.getId()){
				logger.info("Found game with ID: " + game.getId());
				return game;
			}
		}
		iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id == game.getId()){
				logger.info("Found game with ID: " + game.getId());
				return game;
			}
		}
		throw new GameNotFoundException("Could not find a game by the given ID.");
	}	
	
	/**
	 * This method returns one Game from GamesQueued list of the GameManager by the 
	 * username of the creator.
	 * @param username the username of the User who created the game
	 * @return the game, null if not in list
	 * @throws GameNotFoundException 
	 */
	public GroupMeGame getGameQueued(String username) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (username.equals(game.getUsername())){
				return game;
			}
		}
		throw new GameNotFoundException();
	}
	
	/**
	 * This method returns one Game from GamesQueued list of the GameManager by the 
	 * id of the game.
	 * @param id the id of the game
	 * @return the game, null if not in list
	 * @throws GameNotFoundException 
	 */
	public GroupMeGame getGameQueued(long id) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				return game;
			}
		}
		throw new GameNotFoundException();
	}
	
	/**
	 * This method returns one Game from GamesRunning list of the GameManager by the 
	 * username of the creator.
	 * @param username the username of the User who created the game
	 * @return the game, null if not in list
	 * @throws GameNotFoundException 
	 */
	public GroupMeGame getGameRunning(String username) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (username.equals(game.getUsername())){
				return game;
			}
		}
		throw new GameNotFoundException();
	}
	
	/**
	 * This method returns one Game from the GamesRunning list of the GameManager by the 
	 * id of the game.
	 * @param id the id of the game
	 * @return the game, null if not in list
	 * @throws GameNotFoundException 
	 */
	public GroupMeGame getGameRunning(long id) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id == game.getId()){
				return game;
			}
		}
		throw new GameNotFoundException("Could not find a running game by the given ID.");
	}
	
	/**
	 * This method returns one Game from the GamesFinished list of the GameManager by the 
	 * id of the game.
	 * @param id the id of the game
	 * @return the game, null if not in list
	 * @throws GameNotFoundException 
	 */
	public GroupMeGame getGameFinished(long id) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesFinished.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id == game.getId()){
				return game;
			}
		}
		throw new GameNotFoundException("Could not find a finished game by the given ID.");
	}
	
	/**
	 * This method removes a game for a given Username from the list of queued games.
	 * @param username the name of the User who created the game
	 * @throws GameNotFoundException
	 */
	public void removeGameFromQueue(String username) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (username.equals(game.getUsername())){
				this.gamesQueued.remove(game);
				return;
			}
		}
		throw new GameNotFoundException();
	}
	
	/**
	 * This method removes a game for a given gameID from the list of queued games.
	 * @param id the id of the game
	 * @throws GameNotFoundException
	 */
	public void removeGameFromQueue(long id) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesQueued.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				this.gamesQueued.remove(game);
				return;
			}
		}
		throw new GameNotFoundException();
	}
	
	/**
	 * This method removes a game for a given Username from the list of running games.
	 * @param username the name of the User who created the game
	 * @throws GameNotFoundException
	 */
	public void removeGameRunning (String username) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (username.equals(game.getUsername())){
				this.gamesRunning.remove(game);
				return;
			}
		}
		throw new GameNotFoundException();
	}
	
	/**
	 * This method removes a game for a given gameId from the list of running games.
	 * @param id the id of the game
	 * @throws GameNotFoundException
	 */
	public void removeGameRunning (long id) throws GameNotFoundException {
		Iterator<GroupMeGame> iter = this.gamesRunning.iterator();
		while (iter.hasNext()) {
			GroupMeGame game = iter.next();
			if (id==game.getId()){
				this.gamesRunning.remove(game);
				return;
			}
		}
		throw new GameNotFoundException();
	}

	/**
	 * This method switches a game that is in the queue to the running list.
	 * @param gameId the id of the game to make running
	 * @throws GameNotFoundException
	 */
	public void setGameRunning(long gameId) throws GameNotFoundException{
		if (!this.isGameInQueue(gameId)) {
			throw new GameNotFoundException("The Game with id " 
				+ gameId + " is not in the game queue.");
		}
		GroupMeGame tmpGame = this.getGameQueued(gameId);
		logger.info("Removing Game from queue. Game-ID: " + tmpGame.getId());
		this.gamesQueued.remove(tmpGame);
		logger.info("#Games currently queued: " + this.gamesQueued.size());
		logger.info("Adding Game to running list. Game-ID: " + tmpGame.getId());
		this.gamesRunning.add(tmpGame);
		logger.info("#Games currently in running list: " + this.gamesRunning.size());
	}
	
	/**
	 * This method switches a game that is in the running list to the finished list.
	 * @param gameId the id of the game to make running
	 * @throws GameNotFoundException
	 */
	public void setGameFinished(long gameId) throws GameNotFoundException{
		if (!this.isGameRunning(gameId)) {
			throw new GameNotFoundException("The Game with id " 
				+ gameId + " is not in the running queue.");
		}
		GroupMeGame tmpGame = this.getGameRunning(gameId);
		logger.info("Removing Game from running list. Game-ID: " + tmpGame.getId());
		this.gamesRunning.remove(tmpGame);
		logger.info("#Games currently running: " + this.gamesRunning.size());
		logger.info("Adding Game to finished list. Game-ID: " + tmpGame.getId());
		this.gamesFinished.add(tmpGame);
		logger.info("#Games currently in finished list: " + this.gamesFinished.size());
	}
	
	/**
	 * This method sets the users of a UserList object.
	 * @param newGamesRunningList the gamesRunning to set
	 */
	public void setGamesRunning(List<GroupMeGame> newGamesRunningList) {
		this.gamesRunning = newGamesRunningList;
	}

	/**
	 * This method sets the gamesQueued of a GameManager object.
	 * @param newGamesQueuedList the gamesQueued to set
	 */
	public void setGamesQueued(List<GroupMeGame> newGamesQueuedList) {
		this.gamesQueued = newGamesQueuedList;
	}
	
	/**
     * Returns an In-memory instance of a group, which has a valid id.
     * @param topic the topic of the group
     * @param user the owner of the group
     * @return an In-memory instance of a group, which has a valid id.
     */
    public Group createGamingGroup(String topic, User user){
    	Group newGroup = null; 
    	String name = GAME_CONSTANTS.getStandardGameGroupName(topic, user.getUserName());
    	String description = GAME_CONSTANTS.getStandardGameGroupDescription(topic, user.getUserName());
    	try {
    		newGroup =  ObjectFactory.createGroup(name, description);
		} catch (InsertFailedException e) {
			e.printStackTrace();
			logger.error(e);
			return null;
		}
		return ObjectFactory.getInMemoryGroup(newGroup.getId(), name, description);
    }
}
